#  base.py - base object classes
#  Copyright (C) 2005-2007 Sham Chukoury <eleusis@xmms.org>
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.

from sets import Set
import traceback
import sys

class ChalyxObj(object):
	"""
	Base class with support for gobject-like signals.
	"""
	def __init__(self, cfgHandler=None, name=None):
		self.name = name
		self.cfgHandler = cfgHandler
		self.signals = {}
		self.sigTokens = {}
		self.tokenCount = 0
		self.frozen = Set()
	
	def _signamecheck(self, signame):
		if not self.signals.has_key(signame):
			raise NameError("'%s' has no signal '%s'" % (self.name, signame))

	def connect(self, signame, cb, udata=None):
		self._signamecheck(signame)
		self.tokenCount += 1
		self.signals[signame].append(self.tokenCount)
		self.sigTokens[self.tokenCount] = (cb, udata, signame)
		return self.tokenCount

	def disconnect(self, sigtoken):
		if not self.sigTokens.has_key(sigtoken):
			raise KeyError("'%s': Attempt to disconnect invalid token '%i'" % (self.name, sigtoken))
		else:
			self.signals[self.sigTokens[sigtoken][2]].remove(sigtoken)
			del self.sigTokens[sigtoken]
	
	def emit(self, signame, sigdata):
		self._signamecheck(signame)
		if signame in self.frozen:
			return
		for token in self.signals[signame][:]:
			udata = self.sigTokens[token][1]
			self.sigTokens[token][0](self, sigdata, udata)
	
	def freeze_signal(self, signame):
		self._signamecheck(signame)
		self.frozen.add(signame)
	
	def freeze_signals(self):
		for s in self.signals:
			self.freeze_signal(s)
	
	def thaw_signal(self, signame):
		self._signamecheck(signame)
		self.frozen.discard(signame)
	
	def thaw_signals(self):
		for s in self.signals:
			self.thaw_signal(s)

class ProxiedSignal(object):
	"""
	Class holding data for an object that 'proxies'
	or translates a signal from another object.
	Proxying is inactive until a client object of the
	translator connects to the signal (and thus increases
	the signal's refcount)
	"""
	class TokenList(list):
		def __init__(self, setmethod, *args):
			self.setmethod = setmethod
			list.__init__(self, args)

		def append(self, args):
			list.append(self, args)
			self.setmethod(self)

	def __init__(self, name, robj, rsig, handler, udata):
		"""
		name - the signal name
		robj - the remote object
		rsig - the remote signal name
		handler - the method that will do the translation
		udata - user data to pass to handler
		"""
		self.active = False
		self._tokens = ProxiedSignal.TokenList(self.set_tokens)
		self.name = name
		self.robj = robj
		self.rsig = rsig
		self.handler = handler
		self.udata = udata
		self.rtoken = None

	def _check_activate(self):
		assert len(self._tokens) > 0
		if self.active:
			print "Warning: tried to activate signal '%s' while already active" % self.name
			return False
		return True

	def _check_deactivate(self):
		#assert len(self._tokens) <= 0
		if not self.active:
			#print "Warning: tried to deactivate inactive signal '%s'" % self.name
			return False
		return True

	def activate_proxy(self):
		if not self._check_activate(): return
		self.rtoken = self.robj.connect(self.rsig, self.handler, self.udata)
		self.active = True

	def deactivate_proxy(self):
		if not self._check_deactivate(): return
		self.robj.disconnect(self.rtoken)
		self.active = False

	def get_tokens(self):
		return self._tokens

	def set_tokens(self, val):
		self._tokens = val
		if len(self._tokens) > 0 and not self.active:
			self.activate_proxy()
		elif len(self._tokens) <= 0 and self.active:
			self.deactivate_proxy()

	tokens = property(get_tokens, set_tokens)

class SigProxyObj(ChalyxObj):

	def __init__(self, conf=None, name=None):
		ChalyxObj.__init__(self, conf, name)

	### Signal proxy interface ###
	# overrides base class signal interface

	def connect(self, signame, cb, udata=None):
		#print self.name, "connecting signal:", signame, "to", cb.__name__
		self._signamecheck(signame)
		signal = self.signals[signame]
		if not isinstance(signal, ProxiedSignal):
			return ChalyxObj.connect(self, signame, cb, udata)

		self.tokenCount += 1
		self.sigTokens[self.tokenCount] = (cb, udata, signal)
		signal.tokens.append(self.tokenCount)
		
		return self.tokenCount

	def disconnect(self, sigtoken):
		if not self.sigTokens.has_key(sigtoken):
			raise KeyError("'%s': Attempt to disconnect invalid token '%i'" % (self.name, sigtoken))
		else:
			signal = self.sigTokens[sigtoken][2]
			if not isinstance(signal, ProxiedSignal):
				ChalyxObj.disconnect(self, sigtoken)
				return
			signal.tokens.remove(sigtoken)
			del self.sigTokens[sigtoken]

	def emit(self, signame, sigdata):
		self._signamecheck(signame)
		if not isinstance(self.signals[signame], ProxiedSignal):
			ChalyxObj.emit(self, signame, sigdata)
			return
		if signame in self.frozen:
			return
		for token in self.signals[signame].tokens[:]:
			udata = self.sigTokens[token][1]
			try:
				self.sigTokens[token][0](self, sigdata, udata)
			except Exception, detail:
				print "Signal '%s' emission from '%s' failed: %s" % (signame, self.name, detail)
				print "Backtrace:"
				for line in traceback.format_tb(sys.exc_info()[2]):
					print line

	###

	def _faketrans(self, obj, sigdata, altname):
		"""
		Simply re-emits a signal using a different name.
		"""
		self.emit(altname, sigdata)
